using System;
using System.Collections.Generic;
using System.Text;
using org.ngwt.user.client.rpc.impl;
using org.ngwt.user.client.rpc;
using System.Reflection;

namespace org.ngwt.user.server.rpc.impl
{
    #region  Reader Objcets used to provided typed instance readers.
    abstract class ValueReader
    {
        public abstract Object readValue(ServerSerializationStreamReader stream);
    }
    class BooleanReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readBoolean();
        }
    }
    class ByteReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readByte();
        }
    }
    class CharReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readChar();
        }
    }
    class DoubleReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readDouble();
        }
    }
    class FloatReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readFloat();
        }
    }
    class IntReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readInt();
        }
    }
    class LongReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readLong();
        }
    }
    class ObjectReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readObject();
        }
    }
    class ShortReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readShort();
        }
    }
    class StringReader : ValueReader
    {
        override public Object readValue(ServerSerializationStreamReader stream)
        {
            return stream.readString();
        }
    };
    #endregion

    #region  Reader Objcets used to provided Array typed instance readers.
    abstract class VectorReader
    {
        public abstract void read(ServerSerializationStreamReader stream, Object instance);
    }
    class BooleanVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            Boolean[] vector = (Boolean[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readBoolean();
            }
        }
    }
    class ByteVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            byte[] vector = (byte[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readByte();
            }
        }
    }
    class CharVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            char[] vector = (char[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readChar();
            }
        }
    }
    class DoubleVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            double[] vector = (double[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readDouble();
            }
        }
    }
    class FloatVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            float[] vector = (float[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readFloat();
            }
        }
    }
    class IntVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            int[] vector = (int[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readInt();
            }
        }
    }
    class LongVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            long[] vector = (long[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readLong();
            }
        }
    }
    class ObjectVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            object[] vector = (object[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readObject();
            }
        }
    }
    class ShortVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            short[] vector = (short[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readShort();
            }
        }
    }
    class StringVectorReader : VectorReader
    {
        override public void read(ServerSerializationStreamReader stream, Object instance)
        {
            string[] vector = (string[])instance;
            for (int i = 0, n = vector.Length; i < n; ++i)
            {
                vector[i] = stream.readString();
            }
        }
    };
    #endregion

    public class ServerSerializationStreamReader : AbstractSerializationStreamReader
    {

        /**
         * Map of {@link Class} objects to {@link ValueReader}s.
         */
        private static Dictionary<Type, ValueReader> CLASS_TO_VALUE_READER = new Dictionary<Type, ValueReader>();

        /**
         * Map of {@link Class} objects to {@link VectorReader}s.
         */
        private static Dictionary<Type, VectorReader> CLASS_TO_VECTOR_READER = new Dictionary<Type, VectorReader>();

        //private final ClassLoader classLoader;

        private String[] stringTable;

        private  List<String> tokenList = new List<String>();

        //private SerializationPolicy serializationPolicy = RPC.getDefaultSerializationPolicy();
        private SerializationPolicy serializationPolicy = null;

        private int tokenListIndex;

        private SerializationPolicyProvider serializationPolicyProvider;
        private static ObjectReader objReader = new ObjectReader();
        private static ObjectVectorReader objVectorReader = new ObjectVectorReader();

        static ServerSerializationStreamReader()
        {
            CLASS_TO_VECTOR_READER.Add(typeof(Boolean[]),new BooleanVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(byte[]), new ByteVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(char[]), new CharVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(double[]), new DoubleVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(float[]), new FloatVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(int[]), new IntVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(long[]), new LongVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(object[]), objVectorReader);
            CLASS_TO_VECTOR_READER.Add(typeof(short[]), new ShortVectorReader());
            CLASS_TO_VECTOR_READER.Add(typeof(string[]), new StringVectorReader());

            CLASS_TO_VALUE_READER.Add(typeof(Boolean), new BooleanReader());
            CLASS_TO_VALUE_READER.Add(typeof(byte), new ByteReader());
            CLASS_TO_VALUE_READER.Add(typeof(char), new CharReader());
            CLASS_TO_VALUE_READER.Add(typeof(double), new DoubleReader());
            CLASS_TO_VALUE_READER.Add(typeof(float), new FloatReader());
            CLASS_TO_VALUE_READER.Add(typeof(int), new IntReader());
            CLASS_TO_VALUE_READER.Add(typeof(long), new LongReader());
            CLASS_TO_VALUE_READER.Add(typeof(object), objReader);
            CLASS_TO_VALUE_READER.Add(typeof(short), new ShortReader());
            CLASS_TO_VALUE_READER.Add(typeof(string), new StringReader());
        }

        public ServerSerializationStreamReader(SerializationPolicyProvider serializationPolicyProvider)
        {
            //this.classLoader = classLoader;
            this.serializationPolicyProvider = serializationPolicyProvider;
        }

        public Object deserializeValue(Type type)
        {
            ValueReader valueReader = CLASS_TO_VALUE_READER.ContainsKey(type) ? CLASS_TO_VALUE_READER[type] : null;
            if (valueReader != null) 
            {
                return valueReader.readValue(this);
            } 
            else 
            {
                // Arrays of primitive or reference types need to go through readObject.
                return objReader.readValue(this);
            }
        }

        public SerializationPolicy getSerializationPolicy() {
          return serializationPolicy;
        }

        public override void prepareToRead(String encodedTokens) 
        {
          tokenList.Clear();
          tokenListIndex = 0;
          stringTable = null;

          byte[] sep = new byte[] { 191, 191 };
          String sepStr = Encoding.UTF7.GetString(sep);
          int sepLen = sepStr.Length;

          int idx = 0, nextIdx;
          while (-1 != (nextIdx = encodedTokens.IndexOf(sepStr, idx)))
          {
            String current = encodedTokens.Substring(idx, nextIdx - idx-1);
            tokenList.Add(current);
            idx = nextIdx + sepLen;
          }

          base.prepareToRead(encodedTokens);

          // Read the type name table
          //
          deserializeStringTable();

          // If this stream encodes resource file information, read it and get a
          // SerializationPolicy
          if (hasSerializationPolicyInfo()) {
            String moduleBaseURL = readString();
            String strongName = readString();
            if (serializationPolicyProvider != null) {
              serializationPolicy = serializationPolicyProvider.getSerializationPolicy(
                  moduleBaseURL, strongName);

              if (serializationPolicy == null) {
                throw new NullReferenceException(
                    "serializationPolicyProvider.getSerializationPolicy()");
              }
            }
          }
        }

        public override bool readBoolean()
        {
            return !extract().Equals("0");
        }

        public override byte readByte()
        {
            return Byte.Parse(extract());
        }

        public override char readChar()
        {
            // just use an int, it's more foolproof
            return (char)int.Parse(extract());
        }

        public override double readDouble()
        {
            return Double.Parse(extract());
        }

        public override float readFloat()
        {
            return float.Parse(extract());
        }

        public override int readInt()
        {
            return int.Parse(extract());
        }

        public override long readLong()
        {
            return long.Parse(extract());
        }

        public override short readShort()
        {
            return short.Parse(extract());
        }

        public override string readString()
        {
            return getString(readInt());
        }

        protected override object deserialize(string typeSignature)
        {
            Object instance = null;
            SerializedInstanceReference serializedInstRef = SerializabilityUtil.decodeSerializedInstanceReference(typeSignature);
            
            try {
              Type instanceClass = Type.GetType(serializedInstRef.Name);
              if (instanceClass == null)
                  instanceClass = RPC.FindType(serializedInstRef.Name);

              //assert (serializationPolicy != null);

              serializationPolicy.validateDeserialize(instanceClass);

              validateTypeVersions(instanceClass, serializedInstRef);

              Type customSerializer = SerializabilityUtil.hasCustomFieldSerializer(instanceClass);

              instance = instantiate(customSerializer, instanceClass);

              rememberDecodedObject(instance);

              deserializeImpl(customSerializer, instanceClass, instance);

              return instance;

            } catch (Exception e) {
              throw new SerializationException(e.Message);

            } 
        }

        protected override String getString(int index)
        {
          if (index == 0) {
            return null;
          }
          // index is 1-based
          //assert (index > 0);
          //assert (index <= stringTable.length);
          return stringTable[index - 1];
        }


        /**
         * Deserialize an instance that is an array. Will default to deserializing as
         * an Object vector if the instance is not a primitive vector.
         * 
         * @param instanceClass
         * @param instance
         * @throws SerializationException
         */
        private void deserializeArray(Type instanceClass, Object instance)
        {
            //assert (instanceClass.isArray());

            VectorReader instanceReader = CLASS_TO_VECTOR_READER.ContainsKey(instanceClass)
                ? CLASS_TO_VECTOR_READER[instanceClass] : null;
            if (instanceReader != null)
            {
                instanceReader.read(this, instance);
            }
            else
            {
                objVectorReader.read(this, instance);
            }
        }

        private void deserializeClass(Type instanceClass, Object instance)
        {
            PropertyInfo[] serializableProperties = SerializabilityUtil.applyFieldSerializationPolicy(instanceClass);

            foreach (PropertyInfo declProperty in serializableProperties)
            {
                //assert (declField != null);

                Object value = deserializeValue(declProperty.PropertyType);

                //Boolean isAccessible = declField.isAccessible();
                //Boolean needsAccessOverride = !isAccessible
                //    && !Modifier.isPublic(declField.getModifiers());
                //if (needsAccessOverride) {
                //  // Override access restrictions
                //  declField.setAccessible(true);
                //}

                declProperty.SetValue(instance, value,null);

                //if (needsAccessOverride) {
                //  // Restore access restrictions
                //  declField.setAccessible(isAccessible);
                //}
            }

            Type superClass = instanceClass.BaseType;
            if (serializationPolicy.shouldDeserializeFields(superClass))
            {
                deserializeImpl(SerializabilityUtil.hasCustomFieldSerializer(superClass),
                    superClass, instance);
            }
        }

        private void deserializeImpl(Type customSerializer, Type instanceClass, Object instance)
        {
            if (customSerializer != null)
            {
                deserializeWithCustomFieldDeserializer(customSerializer, instanceClass, instance);
            }
            else if (instanceClass.IsArray)
            {
                deserializeArray(instanceClass, instance);
            }
            else
            {
                deserializeClass(instanceClass, instance);
            }
        }

        private void deserializeStringTable()
        {
            int typeNameCount = readInt();
            stringTable = new String[typeNameCount];
            for (int typeNameIndex = 0; typeNameIndex < typeNameCount; ++typeNameIndex)
            {
                stringTable[typeNameIndex] = extract();
            }
        }

        private void deserializeWithCustomFieldDeserializer(
            Type customSerializer, Type instanceClass, Object instance)
        {
            //assert (!instanceClass.isArray());

            Type[] typeArray = new Type[2];
            typeArray[0] = typeof(SerializationStreamReader);
            typeArray[1] = instanceClass;
            MethodInfo deserialize = customSerializer.GetMethod("deserialize", typeArray);
            object[] paramArray = new object[2];
            paramArray[0] = this;
            paramArray[1] = instance;
            deserialize.Invoke(null, paramArray);
        }

        private String extract()
        {
            return tokenList[tokenListIndex++];
        }

        private Object instantiate(Type customSerializer, Type instanceClass)
        {
          if (customSerializer != null) {
            try {
                Type[] typeArray =new Type[1];
                typeArray[0] = typeof(SerializationStreamReader);
                MethodInfo instantiate = customSerializer.GetMethod("instantiate",typeArray);
                object[] paramArray = new object[1];
                paramArray[0] = this;
                return instantiate.Invoke(null,paramArray);
              //Method instantiate = customSerializer.getMethod("instantiate",SerializationStreamReader.class);
              //return instantiate.invoke(null, this);
            } catch (AmbiguousMatchException ) {
              // purposely ignored
            }
          }

          if (instanceClass.IsArray) {
            int length = readInt();
            Type componentType = instanceClass.GetElementType();
            return Array.CreateInstance(componentType, length);
          } else {
            return System.Activator.CreateInstance(instanceClass);
          }
        }

        private void validateTypeVersions(Type instanceClass,SerializedInstanceReference serializedInstRef)
        {
            String clientTypeSignature = serializedInstRef.Signature;
            if (clientTypeSignature.Length == 0) 
            {
                if (shouldEnforceTypeVersioning()) {
                  throw new SerializationException("Missing type signature for "
                      + instanceClass.Name);
            }
            return;
          }

          String serverTypeSignature = SerializabilityUtil.getSerializationSignature(instanceClass);

          if (!clientTypeSignature.Equals(serverTypeSignature)) {
            throw new SerializationException("Invalid type signature for "
                + instanceClass.Name);
          }
        }
    }
}
